Skip to content

Implement player profile creation flow and profile page (#211, #213)#251

Merged
xenobiasoft merged 3 commits into
mainfrom
feature/player-profile-211-213
Apr 30, 2026
Merged

Implement player profile creation flow and profile page (#211, #213)#251
xenobiasoft merged 3 commits into
mainfrom
feature/player-profile-211-213

Conversation

@xenobiasoft

Copy link
Copy Markdown
Owner

Summary

  • Adds UserProfile aggregate, ProfileId value object, and ProfileCreatedEvent/ProfileAliasUpdatedEvent domain events to Sudoku.Domain
  • Implements CreateProfileCommand, UpdateProfileAliasCommand, and GetProfileByAliasQuery handlers with lowercase normalization and alias uniqueness checks
  • Adds CosmosDbUserProfileRepository backed by a new profiles CosmosDB container (provisioned in Bicep) with partition key /alias and unique key policy
  • Adds ProfilesController with POST /api/profiles, GET /api/profiles/{alias}, and PATCH /api/profiles/{alias} endpoints
  • React: updates usePlayerService with full gate + silent migration flow (FR-1, FR-5, FR-9); adds /create-profile and /profile pages
  • Blazor: updates PlayerManager with EnsureProfileInitializedAsync migration flow; adds CreateProfile.razor and Profile.razor pages
  • 18 new unit tests covering UserProfile, CreateProfileCommandHandler, and UpdateProfileAliasCommandHandler; all 728 tests pass

Test plan

  • New user (no localStorage) is redirected to /create-profile and can choose an alias
  • Duplicate alias returns a 409 inline error on the creation form
  • Existing user with sudoku-alias in localStorage is silently migrated to sudoku-profile on next load
  • Existing user with valid sudoku-profile bypasses the creation flow
  • Orphaned profile (localStorage present, CosmosDB document missing) triggers re-create or redirect per FR-9
  • /profile page shows alias and member-since date; inline edit saves new alias and updates localStorage
  • Alias edit returns 409 inline error when new alias is taken
  • All of the above work in both React and Blazor frontends
  • dotnet test passes all 728 tests

Closes #211, #213

🤖 Generated with Claude Code

Adds the full player profile feature: first-visit onboarding gate,
persistent UserProfile aggregate in CosmosDB, silent migration of
legacy alias-only users, and /profile page with alias editing for
both React and Blazor frontends.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements a first-class “user profile” concept (create + view + edit alias) across the backend and both React/Blazor frontends, replacing the legacy auto-generated alias flow with an onboarding gate and migration path.

Changes:

  • Backend: add UserProfile domain aggregate + profile commands/queries + ProfilesController endpoints backed by a new Cosmos DB profiles container.
  • React + Blazor: add /create-profile and /profile pages and implement localStorage-based profile initialization/migration flows.
  • Tests/infra: add unit tests for profile domain/handlers and provision the new Cosmos container via Bicep.

Reviewed changes

Copilot reviewed 49 out of 49 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/frontend/Sudoku.React/src/types/index.ts Adds profile-related TypeScript types.
src/frontend/Sudoku.React/src/pages/ProfilePage.tsx Implements profile display + alias edit UI.
src/frontend/Sudoku.React/src/pages/ProfilePage.module.css Styles for React profile page.
src/frontend/Sudoku.React/src/pages/CreateProfilePage.tsx Implements profile creation UI + localStorage write.
src/frontend/Sudoku.React/src/pages/CreateProfilePage.module.css Styles for React create-profile page.
src/frontend/Sudoku.React/src/hooks/usePlayerService.ts Implements profile gate + legacy alias migration logic.
src/frontend/Sudoku.React/src/api/apiClient.ts Adds profile API calls with status-returning helper.
src/frontend/Sudoku.React/src/App.tsx Registers /create-profile and /profile routes.
src/frontend/Sudoku.Blazor/Services/PlayerManager.cs Adds profile initialization/migration flow for Blazor.
src/frontend/Sudoku.Blazor/Services/LocalStorageService.cs Adds localStorage profile read/write helpers + alias removal.
src/frontend/Sudoku.Blazor/Services/JSRuntimeWrapper.cs Adds localStorage remove wrapper.
src/frontend/Sudoku.Blazor/Services/HttpClients/PlayerApiClient.cs Adds profile create/get/update HTTP calls.
src/frontend/Sudoku.Blazor/Services/HttpClients/IPlayerApiClient.cs Extends client interface with profile operations.
src/frontend/Sudoku.Blazor/Services/Abstractions/IPlayerManager.cs Extends manager interface for profile flows.
src/frontend/Sudoku.Blazor/Services/Abstractions/ILocalStorageService.cs Extends local storage abstraction for profiles.
src/frontend/Sudoku.Blazor/Services/Abstractions/IJSRuntimeWrapper.cs Extends JS runtime abstraction with remove.
src/frontend/Sudoku.Blazor/Models/ProfileInfo.cs Adds local profile info model (id + alias).
src/frontend/Sudoku.Blazor/Models/ProfileDto.cs Adds profile DTO model for API responses.
src/frontend/Sudoku.Blazor/Models/ApiResult.cs Adds status code tracking to API result model.
src/frontend/Sudoku.Blazor/Components/Pages/Profile.razor.cs Implements profile load + alias update logic (code-behind).
src/frontend/Sudoku.Blazor/Components/Pages/Profile.razor Adds Blazor profile page UI.
src/frontend/Sudoku.Blazor/Components/Pages/Index.razor.cs Gates index initialization on profile initialization.
src/frontend/Sudoku.Blazor/Components/Pages/CreateProfile.razor.cs Implements profile creation logic (code-behind).
src/frontend/Sudoku.Blazor/Components/Pages/CreateProfile.razor Adds Blazor create-profile page UI.
src/backend/Tests/Mocks/MockPlayerManagerExtensions.cs Updates mocks to satisfy new profile init calls.
src/backend/Tests/Mocks/MockLocalStorageServiceV2Extensions.cs Updates mocks for new GetProfileAsync behavior.
src/backend/Tests/Domain/UserProfileTests.cs Adds unit tests for UserProfile aggregate behavior/events.
src/backend/Tests/Application/Handlers/UpdateProfileAliasCommandHandlerTests.cs Adds tests for update-alias handler logic.
src/backend/Tests/Application/Handlers/CreateProfileCommandHandlerTests.cs Adds tests for create-profile handler logic.
src/backend/Sudoku.Infrastructure/Repositories/CosmosDbUserProfileRepository.cs Adds CosmosDB-backed repository for profiles.
src/backend/Sudoku.Infrastructure/Models/UserProfileDocument.cs Defines Cosmos document shape for profiles.
src/backend/Sudoku.Infrastructure/Mappers/UserProfileMapper.cs Maps between domain UserProfile and Cosmos document.
src/backend/Sudoku.Infrastructure/Configuration/InfrastructureServiceCollectionExtensions.cs Registers IUserProfileRepository implementation.
src/backend/Sudoku.Domain/ValueObjects/ProfileId.cs Introduces ProfileId value object.
src/backend/Sudoku.Domain/Events/ProfileEvents.cs Adds profile domain events.
src/backend/Sudoku.Domain/Entities/UserProfile.cs Adds the UserProfile aggregate root.
src/backend/Sudoku.Application/Queries/GetProfileByAliasQuery.cs Adds profile read query contract.
src/backend/Sudoku.Application/Interfaces/IUserProfileRepository.cs Adds repository abstraction for profiles.
src/backend/Sudoku.Application/Handlers/UpdateProfileAliasCommandHandler.cs Implements alias update command handler.
src/backend/Sudoku.Application/Handlers/GetProfileByAliasQueryHandler.cs Implements get-profile-by-alias query handler.
src/backend/Sudoku.Application/Handlers/CreateProfileCommandHandler.cs Implements create-profile command handler.
src/backend/Sudoku.Application/DTOs/ProfileDto.cs Adds application-layer ProfileDto.
src/backend/Sudoku.Application/Common/ICommandOfT.cs Adds generic command/handler interfaces.
src/backend/Sudoku.Application/Commands/UpdateProfileAliasCommand.cs Adds update-alias command contract.
src/backend/Sudoku.Application/Commands/CreateProfileCommand.cs Adds create-profile command contract.
src/backend/Sudoku.Api/Models/UpdateProfileAliasRequest.cs Adds PATCH request model.
src/backend/Sudoku.Api/Models/CreateProfileRequest.cs Adds POST request model.
src/backend/Sudoku.Api/Controllers/ProfilesController.cs Adds Profiles API endpoints.
infra/modules/storage.bicep Provisions CosmosDB profiles container + throughput.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/frontend/Sudoku.React/src/pages/CreateProfilePage.tsx
Comment thread src/frontend/Sudoku.Blazor/Components/Pages/Index.razor.cs
Comment thread infra/modules/storage.bicep Outdated
Comment thread src/frontend/Sudoku.React/src/hooks/usePlayerService.ts Outdated
Comment thread src/frontend/Sudoku.Blazor/Services/PlayerManager.cs
Comment thread src/frontend/Sudoku.Blazor/Services/PlayerManager.cs
Comment thread src/backend/Sudoku.Api/Controllers/ProfilesController.cs
…key, and transient error handling

- Rewrite usePlayerService.test.ts to match profile-based initialization flow (fixes CI)
- Fix re-initialization loop after navigate by guarding with navigatingRef
- Change CosmosDB profiles container partition key from /alias to /profileId (stable partition; alias changes no longer require delete+create or leave orphaned documents under old alias partition)
- Add ProfileErrorCodes constants and ErrorCode property to Result<T>/Result to replace brittle substring matching in ProfilesController
- Add same-alias no-op in UpdateProfileAliasCommandHandler (saves unchanged alias was rejected as taken)
- Surface transient backend errors (non-404) as error state rather than redirecting to /create-profile in both React hook and Blazor PlayerManager
- Use Random.Shared instead of new Random() in Blazor PlayerManager suffix retry
- Update CreateProfilePage copy to reflect alias is editable from profile page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@xenobiasoft

Copy link
Copy Markdown
Owner Author

Addressed all Copilot review comments in commit e774f66:

CI failure fixed

  • Rewrote usePlayerService.test.ts to match the new profile-based initialization flow (was still testing old createPlayer API)
  • Fixed a re-initialization loop bug: when navigate('/create-profile') was called without setting isInitialized, the useEffect re-triggered indefinitely — added a navigatingRef guard

Structured error codes (replaces brittle substring matching)

  • Added ProfileErrorCodes static class with AliasTaken, NotFound, SameAlias constants
  • Added ErrorCode property to Result<T> / Result
  • CreateProfileCommandHandler and UpdateProfileAliasCommandHandler now return typed error codes
  • ProfilesController maps using result.ErrorCode == ProfileErrorCodes.X instead of .Contains()

CosmosDB partition key changed from /alias to /profileId

  • Alias changes no longer require delete+create or risk orphaned documents in the old partition
  • GetByIdAsync is now a direct point read (O(1)) instead of a cross-partition query
  • SaveAsync upserts using PartitionKey(document.ProfileId)
  • AliasExistsAsync / GetByAliasAsync continue to use queries (acceptable for infrequent lookups)
  • Removed the unique key policy on /alias from Bicep (was only partition-scoped and therefore ineffective; uniqueness is enforced at application layer via AliasExistsAsync)

Same-alias no-op in UpdateProfileAliasCommandHandler — saving unchanged alias now returns the current profile instead of rejecting as "already taken"

Transient error handling in React hook and Blazor PlayerManager — non-404 backend errors (500/503) now surface as error state / throw instead of redirecting to /create-profile (which would send users with valid profiles to the onboarding flow during transient outages)

Random.Shared instead of new Random() in Blazor PlayerManager suffix retry

UI copy updated — "cannot be changed easily later" replaced with "You can update it later from your profile page"

Note on game migration: the UpdateProfileAliasCommandHandler currently logs "batch update pending" for game alias migration. This is a known deferred item — game documents use PlayerAlias as a key throughout, and a full migration requires either document-level updates or a change to profileId-based game ownership. Leaving this for a dedicated follow-up issue.

The UsePlayerServiceReturn interface now includes profileId as a required
field; existing page test mocks were missing it, causing TypeScript errors
in CI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Azure Static Web Apps: Your stage site is ready! Visit it here: https://gray-bush-02024e71e-251.westus2.7.azurestaticapps.net

@xenobiasoft xenobiasoft merged commit c75da06 into main Apr 30, 2026
11 checks passed
@xenobiasoft xenobiasoft deleted the feature/player-profile-211-213 branch April 30, 2026 20:18
@xenobiasoft xenobiasoft mentioned this pull request Apr 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Profile Creation Flow

2 participants